/*
 *  Copyright 2007 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.service.core.impl;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.common.util.PartialCollection;
import com.blandware.atleap.common.util.QueryInfo;
import com.blandware.atleap.model.core.*;
import com.blandware.atleap.service.core.GroupManager;
import com.blandware.atleap.service.core.RoleManager;
import com.blandware.atleap.service.core.UpgradeManager;
import com.blandware.atleap.service.core.UserManager;
import com.blandware.atleap.service.exception.BeanAlreadyExistsException;
import com.blandware.atleap.service.exception.UnknownVersionException;
import com.blandware.atleap.service.exception.BeanNotFoundException;

import java.util.*;

/**
 * <p>Implementation of UpgradeManager interface.
 * </p>
 * <p><a href="UpgradeManagerImpl.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version : $Revision: 1.4 $ $Date: 2008/07/27 12:43:45 $
 */
public class UpgradeManagerImpl extends BaseManagerImpl implements UpgradeManager {

    // versions
    public static final String VERSION_0_5 = "0.5";
    public static final String VERSION_0_51 = "0.51";
    public static final String VERSION_0_52 = "0.52";
    public static final String VERSION_0_53 = "0.53";
    public static final String VERSION_0_54 = "0.54";
    public static final String VERSION_0_55 = "0.55";
    public static final String VERSION_0_56 = "0.56";
    /**
     * List of versions from which convertion is available. Order is significant:
     * it reflects order of versions succession.
     */
    protected static final List ALLOWED_FROM_VERSIONS;
    static {
        ALLOWED_FROM_VERSIONS = new ArrayList();
        ALLOWED_FROM_VERSIONS.add(UpgradeManagerImpl.VERSION_0_5);
        ALLOWED_FROM_VERSIONS.add(UpgradeManagerImpl.VERSION_0_51);
        ALLOWED_FROM_VERSIONS.add(UpgradeManagerImpl.VERSION_0_52);
        ALLOWED_FROM_VERSIONS.add(UpgradeManagerImpl.VERSION_0_53);
        ALLOWED_FROM_VERSIONS.add(UpgradeManagerImpl.VERSION_0_54);
        ALLOWED_FROM_VERSIONS.add(UpgradeManagerImpl.VERSION_0_55);
        ALLOWED_FROM_VERSIONS.add(UpgradeManagerImpl.VERSION_0_56);
    }

    /**
     * Manager to work with roles
     */
    protected RoleManager roleManager;
    /**
     * Manager to work with groups
     */
    protected GroupManager groupManager;
    /**
     * Manager to work with users
     */
    protected UserManager userManager;

    /**
     * Sets manager to work with roles.
     *
     * @param roleManager   manager
     */
    public void setRoleManager(RoleManager roleManager) {
        this.roleManager = roleManager;
    }

    /**
     * Sets manager to work with groups.
     *
     * @param groupManager  manager
     */
    public void setGroupManager(GroupManager groupManager) {
        this.groupManager = groupManager;
    }

    /**
     * Sets manager to work with users.
     *
     * @param userManager   manager
     */
    public void setUserManager(UserManager userManager) {
        this.userManager = userManager;
    }

    /**
     * @see com.blandware.atleap.service.core.UpgradeManager#upgradeDatabase(String)
     */
    public void upgradeDatabase(String fromVersion)
            throws UnknownVersionException, BeanAlreadyExistsException {
        if (!ALLOWED_FROM_VERSIONS.contains(fromVersion)) {
            throw new UnknownVersionException("Please specify one of allowed versions: " + ALLOWED_FROM_VERSIONS);
        }

        // creating roles
        Set adminsRoles = new HashSet();
        Set managersRoles = new HashSet();
        Set usersRoles = new HashSet();
        int fromVersionIndex = getVersionIndex(fromVersion);
        if (fromVersionIndex <= getVersionIndex(UpgradeManagerImpl.VERSION_0_5)) {
            // forum module roles
            makeRole("forum-forum-assignRoles", "Assign forum roles",
                    "Allows to assign roles to forum", managersRoles);
            makeRole("forum-forum-changeActivity", "Change forum activity",
                    "Allows to make forum active or inactive", managersRoles);
            makeRole("forum-forum-create", "Create forum",
                    "Allows to create a forum", managersRoles);
            makeRole("forum-forum-delete", "Delete forum",
                    "Allows to delete a forum", managersRoles);
            makeRole("forum-forum-list", "List forums",
                    "Allows to access a list of forums", managersRoles);
            makeRole("forum-forum-move", "Move forum",
                    "Allows to move a forum in list", managersRoles);
            makeRole("forum-forum-read", "Use forum",
                    "Allows to use forum (read it, create topics, posts)", usersRoles);
            makeRole("forum-forum-update", "Update forum",
                    "Allows to update a forum properties", managersRoles);
            makeRole("forum-forum-view", "View forum",
                    "Allows to view a forum properties", managersRoles);
            makeRole("forum-post-delete", "Delete post",
                    "Allows to delete forum post", managersRoles);
            makeRole("forum-post-viewEmail", "View post author email",
                    "Allows to view email of post author", managersRoles);
            makeRole("forum-topic-delete", "Delete topic",
                    "Allows to delete forum topic", managersRoles);
            // questionnaire module roles
            makeRole("questionnaire-answerOption-create", "Create answer option",
                    "Allows to create answer option", managersRoles);
            makeRole("questionnaire-answerOption-delete", "Delete answer option",
                    "Allows to delete answer option", managersRoles);
            makeRole("questionnaire-answerOption-list", "List answer options",
                    "Allows to access a list of answer options", managersRoles);
            makeRole("questionnaire-answerOption-move", "Move answer option",
                    "Allows to change answer option position", managersRoles);
            makeRole("questionnaire-answerOption-update", "Update answer option",
                    "Allows to update answer option", managersRoles);
            makeRole("questionnaire-answerOption-view", "View answer option",
                    "Allows to view answer option details", managersRoles);
            makeRole("questionnaire-filledItem-delete", "Delete filled questionnaire",
                    "Allows to delete filled questionnaire", managersRoles);
            makeRole("questionnaire-filledItem-list", "List filled questionnaires",
                    "Allows to access a list of filled questionnaires", managersRoles);
            makeRole("questionnaire-filledItem-view", "View filled questionnaire",
                    "Allows to view filled questionnaire details", managersRoles);
            makeRole("questionnaire-item-changeActivity", "Change questionnaire activity",
                    "Allows to change activity of questionnaire (activate/deactivate)", managersRoles);
            makeRole("questionnaire-item-create", "Create questionnaire",
                    "Allows to create questionnaire", managersRoles);
            makeRole("questionnaire-item-delete", "Delete questionnaire",
                    "Allows to delete questionnaire", managersRoles);
            makeRole("questionnaire-item-fill", "Fill questionnaire",
                    "Allows to fill questionnaire", usersRoles);
            makeRole("questionnaire-item-list", "List questionnaires",
                    "Allows to access a list of questionnaires", managersRoles);
            makeRole("questionnaire-item-update", "Update questionnaire",
                    "Allows to update questionnaire", managersRoles);
            makeRole("questionnaire-item-view", "View questionnaire",
                    "Allows to view questionnaire details", managersRoles);
            makeRole("questionnaire-question-create", "Create question",
                    "Allows to create question", managersRoles);
            makeRole("questionnaire-question-delete", "Delete question",
                    "Allows to delete question", managersRoles);
            makeRole("questionnaire-question-list", "List questions",
                    "Allows to access a list of questions", managersRoles);
            makeRole("questionnaire-question-move", "Move question",
                    "Allows to change question position", managersRoles);
            makeRole("questionnaire-question-update", "Update question",
                    "Allows to update question", managersRoles);
            makeRole("questionnaire-question-view", "View question",
                    "Allows to view question details", managersRoles);
            // statistics roles
            makeRole("core-statistics-view", "View statistics",
                    "Allows to view statistics details", managersRoles);
        }
        if (fromVersionIndex <= getVersionIndex(UpgradeManagerImpl.VERSION_0_51)) {
            // no roles were added from 0.51 to 0.52
        }
        if (fromVersionIndex <= getVersionIndex(UpgradeManagerImpl.VERSION_0_52)) {
            // database download and upload
            makeRole("core-backup-download", "Download database backup",
                    "Allows to download database backup from server", adminsRoles);
            makeRole("core-backup-upload", "Upload database backup",
                    "Allows to upload database backup to server and restore DB state", adminsRoles);
        }
        if (fromVersionIndex <= getVersionIndex(UpgradeManagerImpl.VERSION_0_53)) {
            // sequences roles
            makeRole("core-sequence-create", "Create sequence",
                    "Allows to create sequence", adminsRoles);
            makeRole("core-sequence-delete", "Delete sequence",
                    "Allows to delete sequence", adminsRoles);
            makeRole("core-sequence-deleteOverriden", "Delete overriden sequence",
                    "Allows to delete sequence which overrides owner's sequence", managersRoles);
            makeRole("core-sequence-list", "List sequences",
                    "Allows to access a list of sequences", managersRoles);
            makeRole("core-sequence-override", "Override sequence",
                    "Allows to create sequence which overrides owner's sequence", managersRoles);
            makeRole("core-sequence-update", "Update sequence",
                    "Allows to update sequence", adminsRoles);
            makeRole("core-sequence-updateOverriden", "Update overriden sequence",
                    "Allows to update sequence which overrides owner's sequence", managersRoles);
            makeRole("core-sequence-view", "View sequence",
                    "Allows to view sequence details", managersRoles);
            makeRole("core-sequenceItem-create", "Create sequence item",
                    "Allows to create sequence item", managersRoles);
            makeRole("core-sequenceItem-delete", "Delete sequence item",
                    "Allows to delete sequence item", managersRoles);
            makeRole("core-sequenceItem-list", "List sequence items",
                    "Allows to access a list of sequence items", managersRoles);
            makeRole("core-sequenceItem-move", "Move sequence item",
                    "Allows to change sequence item position", managersRoles);
            makeRole("core-sequenceItem-update", "Update sequence item",
                    "Allows to update sequence item", managersRoles);
            makeRole("core-sequenceItem-view", "View sequence item",
                    "Allows to view sequence item details", managersRoles);
        }

        // adding roles to groups
        addRolesToGroup(adminsRoles, Constants.ADMINS_GROUP);
        addRolesToGroup(managersRoles, Constants.MANAGERS_GROUP);
        addRolesToGroup(usersRoles, Constants.USERS_GROUP);

        if (fromVersionIndex <= getVersionIndex(UpgradeManagerImpl.VERSION_0_53)) {
            // rename role... actually remove old and add new one
            Role role = roleManager.retrieveRole("core-contentLocale-changeActivity");
            if (role != null) {
                List pages = new ArrayList(role.getPages());
                List resources = new ArrayList(role.getResources());
                List menuItems = new ArrayList(role.getMenuItems());
                List groups = new ArrayList(role.getGroups());
                List users = new ArrayList();
                PartialCollection allUsers = userManager.listUsers(new QueryInfo());
                for (Iterator userIterator = allUsers.iterator(); userIterator.hasNext(); ) {
                    User user = (User) userIterator.next();
                    if (user.getFreeRoles().contains(role)) {
                        users.add(user);
                    }
                }

                try {
                    roleManager.deleteRole(role.getName());
                } catch (BeanNotFoundException e) {
                    // just swallow
                }

                role = new Role();
                role.setName("core-contentLocale-changeState");
                role.setTitle("Change content locale state");
                role.setDescription("Allows to change state of content locale (enable, disable or activate it)");
                role.setFixed(Boolean.TRUE);
                roleManager.createRole(role);

                for (Iterator i = pages.iterator(); i.hasNext(); ) {
                    Page page = (Page) i.next();
                    page.addRole(role);
                }
                for (Iterator i = resources.iterator(); i.hasNext(); ) {
                    ContentResource contentResource = (ContentResource) i.next();
                    contentResource.addRole(role);
                }
                for (Iterator i = menuItems.iterator(); i.hasNext(); ) {
                    MenuItem menuItem = (MenuItem) i.next();
                    menuItem.addRole(role);
                }
                for (Iterator i = groups.iterator(); i.hasNext(); ) {
                    Group group = (Group) i.next();
                    group.addRole(role);
                    for (Iterator j = group.getUsers().iterator(); j.hasNext(); ) {
                        User user = (User) j.next();
                        user.addRole(role, group);
                    }
                }
                for (Iterator i = users.iterator(); i.hasNext(); ) {
                    User user = (User) i.next();
                    user.addFreeRole(role);
                    userManager.updateUser(user);
                }
            }
        }
    }

    /**
     * Creates a fixed role. Role is saved in manager and being added to
     * supplied set.
     *
     * @param name          role name
     * @param title         role title
     * @param description   role description
     * @param roles         set of roles to which role will be added after
     *                      creation
     * @return created role
     * @throws com.blandware.atleap.service.exception.BeanAlreadyExistsException
     * if such role already exists
     */
    protected Role makeRole(String name, String title, String description, Set roles) throws BeanAlreadyExistsException {
        Role role = new Role();
        role.setName(name);
        role.setTitle(title);
        role.setDescription(description);
        role.setFixed(Boolean.TRUE);
        roleManager.createRole(role);
        roles.add(role);
        return role;
    }

    /**
     * Adds given roles to group with given name if it exists.
     *
     * @param roles         set of roles to add
     * @param groupName     name of group
     * @throws com.blandware.atleap.service.exception.BeanAlreadyExistsException
     * if some bean already exists
     */
    protected void addRolesToGroup(Set roles, String groupName) throws BeanAlreadyExistsException {
        if (!roles.isEmpty()) {
            Group group = groupManager.retrieveGroup(groupName);
            if (group != null) {
                // list of user that were updated due to roles addition
                Set touchedUsers = new HashSet();

                // add new roles
                for ( Iterator i = roles.iterator(); i.hasNext(); ) {
                    Role role = (Role) i.next();
                    group.addRole(role);
                    List groupUsers = group.getUsers();
                    for (int j = 0; j < groupUsers.size(); j++) {
                        User user = (User) groupUsers.get(j);
                        user.addRole(role, group);
                        touchedUsers.add(user);
                    }
                }

                groupManager.updateGroup(group);
                for (Iterator i = touchedUsers.iterator(); i.hasNext();) {
                    User user = (User) i.next();
                    userManager.updateUser(user);
                }
            }
        }
    }

    /**
     * Calculates index of version in the 'successors' list. If version is not
     * in this list, exception is thrown.
     *
     * @param version   version to work with
     * @return index of version; if index of some version is less then index of
     * another, then first version is earlier than second one
     * @throws UnknownVersionException  thrown if version is not in list
     */
    protected int getVersionIndex(String version) throws UnknownVersionException {
        int index = UpgradeManagerImpl.ALLOWED_FROM_VERSIONS.indexOf(version);
        if (index >= 0) {
            return index;
        } else {
            throw new UnknownVersionException("Unknown version: " + version);
        }
    }
}
